﻿namespace Chapter6

(*
Input ( * )

In this section we will add the ability to zoom in and out of our asteroid field, and also to move the view point with mouse.
We will use the Mouse and Keyboard classes to easily poll the current state of these input devices and we will use matrices to scale and translate our scene appropriately.
*)
module Input =

  open System
  open System.Collections.Generic
  open Microsoft.Xna.Framework
  open Microsoft.Xna.Framework.Graphics
  (*
  We will need the data structures defined in the Microsoft.Xna.Framework.Input, which allow us to read input from:
  - Mouse
  - Keyboard
  - XBox 360 GamePad
  - Windows Phone 7 TouchPad
  *)
  open Microsoft.Xna.Framework.Input
  open Chapter3

  type Chapter2.Math.Vector2< [<Measure>] 'u >
    with
      member this.ToXNAVector = Vector2(this.X |> float32, this.Y |> float32)

  type Game2D() as this = 
    inherit Game()
    let graphics = new GraphicsDeviceManager(this)
    do graphics.GraphicsProfile <- GraphicsProfile.HiDef
    let mutable sprite_batch : SpriteBatch = null
    let mutable asteroid_texture : Texture2D = null
    let mutable asteroids = SmallAsteroidFieldSimulation.f0
    do this.Content.RootDirectory <- "Content"

    (*
    We will maintain two additional fields.
    The first represents the amount of zoom we are using, that is the distance from the plane where the action is taking place.
    The second represents the position over this plane of the camera from which we are observing the scene.
    *)
    let mutable zoom = 1.0f
    let mutable center = Vector2.Zero

    override this.Initialize() = 
      sprite_batch <- new SpriteBatch(this.GraphicsDevice)    
      base.Initialize()
  
    override this.LoadContent() = 
      do asteroid_texture <- this.Content.Load(@"2D\Asteroid")
      base.LoadContent()

    (*
    XNA input is based on the mechanism known as "polling": we will read (poll), at every update, the new state of the input devices we are interested in.
    Polling a device usually gives us a snapshot of that device at the moment of polling;
    we query this snapshot to find the current value of its keys, throttles, joysticks and sensors and take appropriate action.
    *)
    override this.Update gt = 
      asteroids <- SmallAsteroidFieldSimulation.simulation_step asteroids

      (*
      We start by reading the current state of the keyboard.
      The static method Keyboard.GetState returns us a KeyboardState, which contains the current state of each key, which can either be Down or Up.
      We read the state of a key k with the IsKeyDown method, which returns a boolean:
      Keyboard.GetState().IsKeyDown(k)

      or with the indexing operator (same as with an array) which returns a KeyState (Down or Up)
      Keyboard.GetState().[Keys.A]

      When the keys A or S are down then we increase or decrease the zoom field by 1%:
      *)
      if Keyboard.GetState().[Keys.A] = KeyState.Down then
        zoom <- zoom * 1.01f
      elif Keyboard.GetState().[Keys.S] = KeyState.Down then
        zoom <- zoom / 1.01f
      (*
      Similarly to the Keyboard.GetState() method, we use the Mouse.GetState() method to read the current state of the mouse and to store the current position of the mouse cursor in the "center" field:
      *)
      center <- 
        let mouse = Mouse.GetState()
        let x = mouse.X |> float32
        let y = mouse.Y |> float32
        Vector2(x,y)

      base.Update gt

    override this.Draw gt = 
      this.GraphicsDevice.Clear(Color.Black)

      (*
      Rendering code is exactly the same as we have seen before.
      The only difference now is that we use a transformation matrix that will translate (move) and scale (stretch) our asteroids when they are rendered.

      The Begin method of the SpriteBatch also has many overloads, that change the way rendering will be performed.
      The parameters of the Begin method are a relatively advanced topic, so we have just specified here a series of reasonable default parameters.
      The most important aspect of the Begin method we are using now is the last parameter.
      We specify a transform matrix that translates all rendered asteroids so that the mouse cursor is at the origin of the simulation, and which then scales all the asteorids in the x and y (along the screen plance) according to our zooming factor.
      Notice that if we scaled along the z axis, rendering could stop working, since the SpriteBatch relies on z values being in a precise range:
      *)
      sprite_batch.Begin(
        SpriteSortMode.Deferred,
        BlendState.AlphaBlend,
        SamplerState.LinearClamp,
        DepthStencilState.None,
        RasterizerState.CullNone,
        null,
        Matrix.CreateTranslation(-center.X,-center.Y,0.0f) * Matrix.CreateScale(zoom,zoom,1.0f))

      let field_size = float32(Chapter3.SmallAsteroidFieldSimulation.field_size)
      for (a,i) in Seq.mapi (fun i a -> a,i) asteroids do
        do sprite_batch.Draw(
            asteroid_texture,
            Vector2(800.0f,480.0f) * a.Position.ToXNAVector / field_size, 
            Nullable<_>(),
            Color.White, 
            0.0f, 
            Vector2.Zero, 
            0.01f, 
            SpriteEffects.None,
            0.0f)

      sprite_batch.End()

      base.Draw gt
